# 使用python程序爬取百度图片网站按关键词搜索的图片，并存储到指定目录下。
# 第八周作业3)
# 班级：Python五期
# 学员：李子坚

import os,time,json,math
import requests
from requests.exceptions import RequestException
from urllib.parse import urlencode
from urllib.request import urlretrieve

#解码图片URL用的字符串对应表
str_table = {
    '_z2C$q': ':',
    '_z&e3B': '.',
    'AzdH3F': '/'
}

#解码图片URL用的字符对应表
char_table = {
    'w': 'a',
    'k': 'b',
    'v': 'c',
    '1': 'd',
    'j': 'e',
    'u': 'f',
    '2': 'g',
    'i': 'h',
    't': 'i',
    '3': 'j',
    'h': 'k',
    's': 'l',
    '4': 'm',
    'g': 'n',
    '5': 'o',
    'r': 'p',
    'q': 'q',
    '6': 'r',
    'f': 's',
    'p': 't',
    '7': 'u',
    'e': 'v',
    'o': 'w',
    '8': '1',
    'd': '2',
    'n': '3',
    '9': '4',
    'c': '5',
    'm': '6',
    '0': '7',
    'b': '8',
    'l': '9',
    'a': '0'
}

#将字符对应表的字符转换为十进制unicode编码，以方便使用字符串的translate方法
char_table = {ord(key): ord(value) for key, value in char_table.items()}

def decrypt(encrypt_url):
    '''解码经过加密的图片URL'''
    #先按字符串对应表进行替换
    for key, value in str_table.items():
        encrypt_url = encrypt_url.replace(key, value)
        
    #再按字符对应表替换剩下的字符
    return encrypt_url.translate(char_table)

def mkdir(path):
    '''创建文件夹'''
    #去除首位空格
    path=path.strip()
    #去除尾部 \ 符号
    path=path.rstrip("\\")
 
    #判断路径是否存在，如果不存在则创建文件夹
    if not os.path.exists(path):
        os.makedirs(path)

def getPage(keyword,offset):
    '''爬取网页信息'''
    #定义header头信息
    headers = {
        'Accept-Encoding': 'gzip, deflate, br',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36',
    }

    #定义参数
    params={
        'tn': 'resultjson_com',
        'ipn': 'rj',
        'ct': '201326592',
        'is': '',
        'fp': 'result',
        'queryWord': '',
        'cl': '2',
        'lm': '-1',
        'ie': 'utf-8',
        'oe': 'utf-8',
        'adpicid': '',
        'st': '-1',
        'z': '',
        'ic': '0',
        'word': keyword,
        's': '',
        'se': '',
        'tab': '',
        'width': '',
        'height': '',
        'face': '0',
        'istype': '2',
        'qc': '',
        'nc': '1',
        'fr': '',
        'pn': str(offset),
        'rn': '30',
        'gsm': '5a',
        '1530204120191':'',
    }
    
    #初始化url地址
    url = 'https://image.baidu.com/search/acjson?'+urlencode(params)
    try:
        #发起GET请求，执行爬取
        res = requests.get(url, headers=headers)
        #判断并返回结果
        if res.status_code == 200:
            str_json = res.content.decode('utf-8')
            return json.loads(str_json)
    except:
        return None

def getImage(jsondata):
    '''获取图片信息'''
    data = jsondata.get("data")
    if data:
        for item in data:
            if item != {}:
                yield {
                    'index': int(item.get("pageNum")) + 1,      #序号
                    'thumbURL': item.get("thumbURL"),           #小图
                    'middleURL': item.get("middleURL"),         #中图
                    'largeURL': decrypt(item.get("objURL")),    #大图
                    'title': item.get("fromPageTitleEnc"),      #图片标题
                }

def saveImage(keyword, item):
    '''下载并储存图片信息'''
    img_url = item.get("largeURL")                              #图片下载URL
    #替换图片标题里的特殊字符
    title = item.get("title").replace('\\','_').replace('\/','_').replace(':','_').replace('*','_').replace('?','_').replace('\"','_').replace('<','_').replace('>','_').replace('|','_')
    #图片路径的处理
    folder_path = os.path.join("./baiduimg/", keyword)
    file_name = str(item.get("index")) + ")" + title + ".jpg"
    file_path = os.path.join(folder_path, file_name)

    try:
        #创建response对象
        res = requests.get(img_url, timeout=15) 
        if res.status_code == 200:  #大图获取成功
            pass
        else:                       #大图获取失败，则获取中图
            res = requests.get(item.get("middleURL"), timeout=15)

        if res.status_code == 200:  #图片获取成功
            #保存图片文件
            with open(file_path,'wb') as f:
                f.write(res.content)
        else:
            print("下载图片《%s》失败！"%(item.get("title")))

    except RequestException:	
        print("下载图片《%s》失败！"%(item.get("title")))

def main(keyword, offset, img_num):
    '''主程序函数，负责调度执行爬取处理'''
    
    #爬取百度图片网页并逐项下载图片文件
    jsondata = getPage(keyword, offset)
    for item in getImage(jsondata): #执行解析并遍历
        if int(item['index']) <= img_num:
            #逐行输出图片信息
            print("{:<6}{:<100}{:<40}".format(item['index'],item['largeURL'],item['title']))

            #下载图片文件
            saveImage(keyword, item)
        else:
            break

#判断当前执行是否为主程序，并循环调度主函数来爬取信息
if __name__ == "__main__":
    #创建存放百度图片文件夹
    mkdir("./baiduimg")

    while True:
        #输入关键词和下载图片数量
        keyword = input("亲，请输入需要搜索百度图片的关键词（输入Q或q退出）：")
        if keyword == 'q' or keyword == 'Q':
            break
        else:
            img_num = int(input("亲，请输入需要下载百度图片的数量："))

            #处理图片的存储路径./baiduimg/<keyword>
            path = os.path.join("./baiduimg/", keyword)
            #按关键字创建图片存储文件夹
            mkdir(path)

            #输出标题行
            print("{:<4}{:<98}{:<46}".format("序号","图片URL","图片标题"))
            #开始爬取百度图片数据
            for i in range(math.ceil(img_num/30)):
                main(keyword, i*30, img_num)
                time.sleep(1)

            print("==========按关键词<%s>搜索的百度图片下载完成！=========="%(keyword))
            print("=================请查看图片文件夹<%s>！================="%(keyword))
